#include "stdafx.h"
#include "collabrc.h"
#include "RecentChange.h"
#include "RollbackAction.h"
#include <QNetworkAccessManager>
#include "MediawikiXmlReader.h"
#include "WarnAction.h"

QNetworkAccessManager CollabRC::manager;

CollabRC::CollabRC(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags),m_loginComplete(false),m_collaboration(GetIrcName()),m_rc(GetIrcName(), GetRcChannel()),m_queue(this),
m_currentChange(NULL)
{
	ui.setupUi(this);
	connect(&m_collaboration, SIGNAL(LogEvent(CLoggedEvent*)),
					    SLOT(AddEvent(CLoggedEvent*)));
	m_collaboration.connect(this, SIGNAL(destroyed(QObject*)), SLOT(Disconnect()));
	m_rc.connect(this, SIGNAL(destroyed(QObject*)), SLOT(Disconnect()));
	m_queue.connect(this, SIGNAL(destroyed(QObject*)), SLOT(Shutdown()));
	connect(&m_collaboration, SIGNAL(ConnectionEstablished()), SLOT(CollaborationConnectionEstablished()));
	qDebug("Starting IRC collaboration threads");
	m_collaboration.start();
	m_rc.start();
	m_queue.start();
	ui.logTable->setModel(&m_logModel);
	ui.logTable->verticalHeader()->setMinimumHeight(18);
	ui.logTable->verticalHeader()->setDefaultSectionSize(18);
	ui.logTable->horizontalHeader()->setDefaultSectionSize(135);

	QStringList list;
	list.append("Timestamp");
	list.append("Event");
	list.append("Type");
	list.append("Details");
	m_logModel.setHorizontalHeaderLabels(list);

	ui.logTable->connect(this, SIGNAL(LogUpdated()), SLOT(reset()));
	connect(&m_rc, SIGNAL(RecentChange(QString, QString, unsigned int, int)), SLOT(RecentChange(QString, QString, unsigned int, int)));
	m_collaboration.connect(this, SIGNAL(AcceptedChange(CRecentChange*)), SLOT(AcceptChange(CRecentChange*)));
	connect(this, SIGNAL(AcceptedChange(CRecentChange*)), SLOT(ChangeAccepted(CRecentChange*)));
	connect(&m_collaboration, SIGNAL(FlagEdit(unsigned int, float, QString, QString)), SLOT(ChangeFlagged(unsigned int, float, QString, QString)));
	connect(ui.recentChangesList, SIGNAL(itemSelectionChanged()), SLOT(RecentChangeSelected()));

	// Hook up buttons
	connect(ui.actionRevert, SIGNAL(triggered()), SLOT(Rollback()));
	connect(ui.actionPass, SIGNAL(triggered()), SLOT(Pass()));
	connect(ui.actionRevert_and_Warn, SIGNAL(triggered()), SLOT(Warn()));
}

CollabRC::~CollabRC()
{

}

void CollabRC::AddEvent( CLoggedEvent* event )
{
	// Build event data and add it in
	EventData data;
	data.details = event->GetDetails();
	data.type = event->GetType();
	data.timestamp = time(NULL);
	data.name = event->GetName();

	m_log.append(data);

	// Add a cross-reference
	m_logDictionary[event->GetId()] = m_logModel.rowCount();

	// Hook up update events
	connect(event, SIGNAL(StateUpdated(CLoggedEvent*)), SLOT(UpdateEvent(CLoggedEvent*)));
	connect(event, SIGNAL(destroyed(QObject *)), SLOT(EventDestroyed(QObject *)));
	
	// Update the model
	QDateTime timestamp = QDateTime::currentDateTime();
	QList<QStandardItem*> newEvent;
	newEvent.append(new QStandardItem(timestamp.toString(Qt::LocalDate)));
	newEvent.append(new QStandardItem(data.name));
	newEvent.append(BuildMessageType(event));
	newEvent.append(new QStandardItem(data.details));
	m_logModel.appendRow(newEvent);
	ui.logTable->scrollToBottom();
}

void CollabRC::UpdateEvent(CLoggedEvent* event)
{/*
	// Look up the event
	
	
	m_logModel.item(index, 0)->setData("1");
	m_logModel.item(index, 1)->setData(event->GetName());
	m_logModel.item(index, 2)->setData("MESSAGE");
	m_logModel.item(index, 3)->setData(event->GetDetails()); */

	int index = m_logDictionary[event->GetId()];

	QDateTime timestamp = QDateTime::currentDateTime();
	QList<QStandardItem*> newEvent;
	newEvent.append(new QStandardItem(timestamp.toString(Qt::LocalDate)));		
	newEvent.append(new QStandardItem(event->GetName()));
	newEvent.append(BuildMessageType(event));
	newEvent.append(new QStandardItem(event->GetDetails()));

	// TODO: Make this more graceful
	m_logModel.insertRow(index, newEvent);
	m_logModel.removeRow(index + 1);

	emit LogUpdated();
}

void CollabRC::EventDestroyed( QObject* event )
{
	CLoggedEvent* loggedEvent = static_cast<CLoggedEvent*>(event);
	int id = loggedEvent->GetId();
	// Remove from the dictionary
	m_logDictionary.remove(id);
}

QStandardItem* CollabRC::BuildMessageType( CLoggedEvent* event )
{
	// TODO: I18N
	QString text;
	QColor color;
	switch (event->GetType())
	{
	case CLoggedEvent::PROGRESS:
		text = "In progress";
		color = Qt::blue;
		break;
	case CLoggedEvent::COMPLETE:
		text = "Done";
		color = Qt::darkGreen;
		break;
	case CLoggedEvent::ERROR:
		text = "Error";
		color = Qt::red;
		break;
	case CLoggedEvent::WARNING:
		text = "Warning";
		color = Qt::darkYellow;
		break;
	case CLoggedEvent::INFO:
		text = "Info";
		color = Qt::black;
		break;
	default:
		text = "Unknown type";
		color = Qt::red;
		break;
	}

	QStandardItem *item = new QStandardItem(text);
	return item;
	
}

void CollabRC::RecentChange( QString page, QString user, unsigned int diff, int change )
{
	CRecentChange *changeItem = new CRecentChange(/* ui.recentChangesList */ NULL);
	changeItem->SetPageName(page);
	changeItem->SetEditor(user);
	changeItem->SetDiff(diff);
	changeItem->SetChange(change);
	m_changeList[diff] = changeItem;

	// Add it to the list if it's not too long already
	if (ui.recentChangesList->count() < MAX_PENDING_CHANGES)
	{
		emit AcceptedChange(changeItem);
	}
}

void CollabRC::ChangeFlagged( unsigned int diff, float probability, QString bot, QString remarks )
{
	// Find the item
	CRecentChange* change = m_changeList[diff];
	if (change == NULL)
		return;

	// Can we accept it?
	int row = ui.recentChangesList->row(change);
	if (ui.recentChangesList->count() < MAX_PENDING_CHANGES * FLAGGED_CHANGE_FACTOR || ui.recentChangesList->row(change) >= 0)
	{
		// Update the details
		change->SetProbability(probability >= 0.0f ? probability : 0.5f);
		change->SetRemark(change->GetRemark() + " " + remarks);
		change->SetBot(change->GetBot() + " " + bot);
		// Remove it before re-adding it
		ui.recentChangesList->removeItemWidget(change);
		emit AcceptedChange(change);
	}
}

void CollabRC::ChangeAccepted(CRecentChange* changeItem)
{
	// TODO: Make sure we don't add duplicate pages due to multiple changes to the same page

	// Find which row to insert at
	// TODO: A binary search would be more efficient
	int idx = -1;
	for (int i = 0; i < ui.recentChangesList->count(); ++i)
	{
		CRecentChange *row = static_cast<CRecentChange*>(ui.recentChangesList->item(i));
		if (row->GetProbability() <= changeItem->GetProbability())
		{
			idx = i;
			break;
		}
	}

	if (idx == -1)
		ui.recentChangesList->addItem(changeItem);
	else
		ui.recentChangesList->insertItem(idx, changeItem);

	ui.recentChangesList->update();
}

void CollabRC::RecentChangeSelected()
{
	static QMutex signalBlockMutex(QMutex::Recursive);
	static volatile bool running = false;

	signalBlockMutex.lock();
	if (!running)
	{
		running = true;
		if (ui.recentChangesList->currentRow() >= 0)
		{
			// Look up the selected item
			CRecentChange* change = static_cast<CRecentChange*>(ui.recentChangesList->selectedItems().first());
			m_currentChange = change;

			// Update the header data
			ui.titleLabel->setText(change->GetPageName());
			ui.pageEditBox->setText(change->GetPageName());
			ui.authorEditBox->setText(change->GetEditor());

			int bytes = change->GetChange();
			QString bytesStr;
			QTextStream(&bytesStr)  << (bytes > 0 ? "+" : "")
									<< bytes;

			ui.byteChangeLabel->setText(bytesStr);
			if (bytes > 0)
			{
				change->setTextColor(Qt::darkGreen);
			}
			else if (bytes < 0)
			{
				change->setTextColor(Qt::red);
			}
			else 
			{
				change->setTextColor(Qt::black);
			}



			QString botFlag = change->GetBot();
			if (!botFlag.isEmpty())
			{
				QString remark = change->GetRemark();
				if (remark.isEmpty())
				{
					ui.flagLabel->setText("Flagged by " + botFlag);
				}
				else 
				{
					ui.flagLabel->setText("Flagged by " + botFlag + ": " + remark);
				}
			}
			else 
			{
				ui.flagLabel->setText("");
			}

			// Load the diff
			QString uri;
			QTextStream uriStream(&uri);
			uriStream << "http://en.wikipedia.org/w/index.php?diff=" << change->GetDiff()
				<< "&diffonly=true";
			
			QUrl url(uri);
			ui.diffView->setUrl(url);

			// Remove the selected item
			ui.recentChangesList->takeItem(ui.recentChangesList->currentRow());
			ui.recentChangesList->clearSelection();
			ui.recentChangesList->update();
		}
		running = false;
	}
	signalBlockMutex.unlock();
}

void CollabRC::Rollback()
{
	// Build a new action and queue it up
	CRollbackAction* action = new CRollbackAction(m_currentChange->GetDiff(), m_currentChange->GetPageName());
	m_collaboration.connect(action, SIGNAL(RollbackSucceeded(unsigned int)), SLOT(MarkBad(unsigned int)));
	connect(action, SIGNAL(EventPosted(CLoggedEvent*)), SLOT(AddEvent(CLoggedEvent*)));
	m_queue.Enqueue(action, true);

	// Move to the next edit
	SelectNext();
}

void CollabRC::Warn()
{
	IActionItem *action = new CWarnAction(m_currentChange->GetDiff(), m_currentChange->GetPageName(), m_currentChange->GetEditor());
	m_collaboration.connect(action, SIGNAL(RollbackSucceeded(unsigned int)), SLOT(MarkBad(unsigned int)));
	connect(action, SIGNAL(EventPosted(CLoggedEvent*)), SLOT(AddEvent(CLoggedEvent*)));
	m_queue.Enqueue(action, true);

	// Move to the next edit
	SelectNext();
}

void CollabRC::Pass()
{
	if (m_currentChange)
		m_collaboration.MarkGood(m_currentChange->GetDiff());

	SelectNext();
}

void CollabRC::CollectLoginDetails()
{
	static QDialog dialog;
	if (!m_loginComplete)
	{
		QEventLoop loop;
		login.setupUi(&dialog);

		while (!m_loginComplete)
		{
			dialog.show();
			if (dialog.exec() == QDialog::Rejected)
			{
				exit(1);
			}

			// Verify the login
			QString uri = "http://en.wikipedia.org/w/api.php?action=login&format=xml";
			QUrl url = uri;
			QNetworkRequest req = CollabRC::CreateNetworkRequest(url);

			QString loginDetails = "lgname=";
			loginDetails += QUrl::toPercentEncoding(login.username->text());
			loginDetails += "&lgpassword=";
			loginDetails += QUrl::toPercentEncoding(login.password->text());
			
			QNetworkReply *reply = manager.post(req, loginDetails.toAscii());

			// Wait for the response
			QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
			loop.exec();

			//QString data = reply->readAll();

			// Read in the response
			CMediawikiXmlReader xmlHandler(NULL);
			QXmlSimpleReader reader;
			QXmlInputSource source(reply);
			reader.setContentHandler(&xmlHandler);
			reader.parse(source);

			// Check that we logged in successfully
			while (xmlHandler.errorOccurred())
			{
				// If we got an error, it may be due to needing a login token
				if (xmlHandler.GetErrorCode() == "NeedToken")
				{
					QString tokenUrl = "http://en.wikipedia.org/w/api.php?action=login&format=xml";
					QString tokenDetails = "lgname=";
					tokenDetails += QUrl::toPercentEncoding(login.username->text());
					tokenDetails += "&lgpassword=";
					tokenDetails += QUrl::toPercentEncoding(login.password->text());
					tokenDetails += "&lgtoken=";
					tokenDetails += QUrl::toPercentEncoding(xmlHandler.GetLoginToken());					

					xmlHandler.ClearError();
					QNetworkRequest request = CollabRC::CreateNetworkRequest(tokenUrl);
					QNetworkReply *reply = manager.post(request, tokenDetails.toAscii());

					loop.connect(reply, SIGNAL(finished()), SLOT(quit()));
					loop.exec();

					// Reparse
					reader.parse(QXmlInputSource(reply));

				}
				else 
				{
					// Show error message
					QMessageBox mb(QMessageBox::Critical, "Error", xmlHandler.GetError(), QMessageBox::Ok);
					mb.exec();
				}
			}
			
			if (!xmlHandler.errorOccurred())
				m_loginComplete = true;
		}
	}
}

void CollabRC::LoginCancelled()
{
	exit(1);
}

QString CollabRC::GetIrcName()
{
	CollectLoginDetails();
	return GetNetworkCode() + login.username->text();
}

QString CollabRC::GetRcChannel() 
{
	CollectLoginDetails();
	if (login.network->currentText() == "en.wikipedia.org")
		return "#en.wikipedia";
	else
		return "";
}

QString CollabRC::GetNetworkCode()
{
	CollectLoginDetails();
	if (login.network->currentText() == "en.wikipedia.org")
		return "enwp";
	else
		return "____";
}

void CollabRC::SelectNext()
{
	ui.recentChangesList->setCurrentRow(0);
}

void CollabRC::CollaborationConnectionEstablished()
{
	// Update the status window
	ui.connectionStatusLabel->setText("Connected");
}

void CollabRC::UpdateCollaborationUsers( unsigned int nBots, unsigned int nClients )
{
	// Update the status window
	QString str;
	QTextStream stream(&str);
	stream << nBots << " bots, "
			<< nClients << " clients";
	ui.collaboratorsLabel->setText(str);
}

QNetworkRequest CollabRC::CreateNetworkRequest(const QUrl& url )
{
	QNetworkRequest request(url);
	request.setRawHeader("User-Agent", "CollabRC/0.1 Qt4 " CRC_CONFIGURATION " [[User:Shirik/CollabRC]]");
	return request;
}

